package com.fengpb.friday.conductor.core.execution.tasks;

import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fengpb.friday.conductor.common.metadata.run.Workflow;
import com.fengpb.friday.conductor.common.metadata.task.Task;
import static com.fengpb.friday.conductor.common.metadata.task.Task.*;
import com.fengpb.friday.conductor.common.remote.RemoteClient;
import com.fengpb.friday.conductor.core.execution.WorkflowExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Component
public class HttpTask extends WorkflowSystemTask {

    private static final Logger log = LoggerFactory.getLogger(HttpTask.class);

    public static final String NAME = "HTTP";

    public static final String REQUEST_PARAMETER_NAME = "http_request";

    static final String MISSING_REQUEST = "Missing HTTP request. Task input MUST have a '" + REQUEST_PARAMETER_NAME + "' key with HttpTask.Input as value. See documentation for HttpTask for required input parameters";

    protected ObjectMapper objectMapper = new ObjectMapper();

    private final TypeReference<Map<String, Object>> mapOfObj = new TypeReference<Map<String, Object>>(){};

    private final TypeReference<List<Object>> listOfObj = new TypeReference<List<Object>>(){};

    public HttpTask(RemoteClient remoteClient) {
        super(NAME);
        this.remoteClient = remoteClient;
    }

    private final RemoteClient remoteClient;

    @Override
    public void execute(Workflow workflow, Task task, WorkflowExecutor executor) {
        Object request = task.getInputData().get(REQUEST_PARAMETER_NAME);
        if(request == null) {
            task.setReasonForInCompletion(MISSING_REQUEST);
            task.setStatus(Status.FAILED);
            return;
        }

        Input input = objectMapper.convertValue(request, Input.class);
        if(input.getUri() == null) {
            String reason = "Missing HTTP URI.  See documentation for HttpTask for required input parameters";
            task.setReasonForInCompletion(reason);
            task.setStatus(Status.FAILED);
            return;
        }

        if(input.getMethod() == null) {
            String reason = "No HTTP method specified";
            task.setReasonForInCompletion(reason);
            task.setStatus(Status.FAILED);
            return;
        }

        if(!StringUtils.hasText(input.getServiceName())) {
            String reason = "No service specified";
            task.setReasonForInCompletion(reason);
            task.setStatus(Status.FAILED);
            return;
        }

        HttpHeaders headers = new HttpHeaders();
        input.getHeaders().forEach(headers::add);
        try {
            String responseStr = remoteClient.call(input.serviceName, input.uri, input.method, String.class, JSON.toJSONString(input.getBody()), headers);
            if (StringUtils.hasText(responseStr)) {
                Object response = extractBody(responseStr);
                if (response != null) {
                    task.getOutputData().put("response", response);
                }
                task.setStatus(Status.COMPLETED);
            } else {
                task.setReasonForInCompletion("response is blank");
                task.setStatus(Status.COMPLETED_WITH_ERRORS);
            }
        } catch (Exception e) {
            log.error("remote service:{}, uri:{} error, reason:{}", input.getServiceName(), input.getUri(), e.getMessage(), e);
            task.setStatus(Status.FAILED);
            task.setReasonForInCompletion(e.getMessage());
        }
    }

    private Object extractBody(String json) {
        try {
            JsonNode node = objectMapper.readTree(json);
            if (node.isArray()) {
                return objectMapper.convertValue(node, listOfObj);
            } else if (node.isObject()) {
                return objectMapper.convertValue(node, mapOfObj);
            } else if (node.isNumber()) {
                return objectMapper.convertValue(node, Double.class);
            } else {
                return node.asText();
            }
        } catch (IOException jpe) {
            log.error("Error extracting response body", jpe);
            return json;
        }
    }

    @Override
    public boolean isAsync(Task task) {
        return task.isAsync();
    }

    public static class Input {
        private HttpMethod method;	//PUT, POST, GET, DELETE, OPTIONS, HEAD
        private String vipAddress;
        private String serviceName;
        private Map<String, String> headers = new HashMap<>();
        private String uri;
        private Object body;
        private MediaType accept = MediaType.APPLICATION_JSON;
        private MediaType contentType = MediaType.APPLICATION_JSON;
        private String oauthConsumerKey;
        private String oauthConsumerSecret;
        private  Integer connectionTimeOut;
        private Integer  readTimeOut;

        public HttpMethod getMethod() {
            return method;
        }

        public void setMethod(HttpMethod method) {
            this.method = method;
        }

        public String getVipAddress() {
            return vipAddress;
        }

        public void setVipAddress(String vipAddress) {
            this.vipAddress = vipAddress;
        }

        public String getServiceName() {
            return serviceName;
        }

        public void setServiceName(String serviceName) {
            this.serviceName = serviceName;
        }

        public Map<String, String> getHeaders() {
            return headers;
        }

        public void setHeaders(Map<String, String> headers) {
            this.headers = headers;
        }

        public String getUri() {
            return uri;
        }

        public void setUri(String uri) {
            this.uri = uri;
        }

        public Object getBody() {
            return body;
        }

        public void setBody(Object body) {
            this.body = body;
        }

        public MediaType getAccept() {
            return accept;
        }

        public void setAccept(MediaType accept) {
            this.accept = accept;
        }

        public MediaType getContentType() {
            return contentType;
        }

        public void setContentType(MediaType contentType) {
            this.contentType = contentType;
        }

        public String getOauthConsumerKey() {
            return oauthConsumerKey;
        }

        public void setOauthConsumerKey(String oauthConsumerKey) {
            this.oauthConsumerKey = oauthConsumerKey;
        }

        public String getOauthConsumerSecret() {
            return oauthConsumerSecret;
        }

        public void setOauthConsumerSecret(String oauthConsumerSecret) {
            this.oauthConsumerSecret = oauthConsumerSecret;
        }

        public Integer getConnectionTimeOut() {
            return connectionTimeOut;
        }

        public void setConnectionTimeOut(Integer connectionTimeOut) {
            this.connectionTimeOut = connectionTimeOut;
        }

        public Integer getReadTimeOut() {
            return readTimeOut;
        }

        public void setReadTimeOut(Integer readTimeOut) {
            this.readTimeOut = readTimeOut;
        }
    }
}
